Skip to content

TML-2785: M:N slice 1 — correlated include read through the junction#679

Open
tensordreams wants to merge 6 commits into
tml-2784-slice-0-contract-resolver-foundationfrom
tml-2785-slice-1-correlated-read
Open

TML-2785: M:N slice 1 — correlated include read through the junction#679
tensordreams wants to merge 6 commits into
tml-2784-slice-0-contract-resolver-foundationfrom
tml-2785-slice-1-correlated-read

Conversation

@tensordreams
Copy link
Copy Markdown
Contributor

Slice 1 of the SQL ORM: Many-to-Many End to End project (Linear project). Reads an M:N relation through its junction.

Stacked PR. Base is tml-2784 (#678, slice 0) → tml-2597 (#673) → tml-2729 (#667) → main. Review/merge bottom-up.

Overview

db.orm.User.include(tags) now resolves a many-to-many relation to { …user, tags: Tag[] } in a single correlated subquery that walks parent → junction → target — no LATERAL, no second query. Built on slice 0s ResolvedRelation.through.

Changes (4 commits)

  • fcecac5b3 — integration fixture gains a User ↔ Tag M:N relation via a UserTag junction (composite PK user_id/tag_id); contract.json/.d.ts re-emitted.
  • e587b433c — read path: IncludeExpr.through (surfaced by resolveIncludeRelation), and buildCorrelatedIncludeProjection gains an M:N branch — buildManyToManyJunctionArtifacts builds a non-LATERAL inner join to the junction (junction.childColumns = target.targetColumns) correlated to the parent (junction.parentColumns = parent anchor), composite-key AND-ed; FK decode path reused. Unit-tested at the AST level.
  • b9c3e9f7b — replace 2 bare as casts with castAs; add the missing M:N + distinct + non-leaf unit test.
  • d3232cbad — 7 integration tests (PGlite).

Integration tests (per the project standard)

Whole-row toEqual; 6/7 use explicit .select(...) (so adding a model field wont churn assertions); test 5 uses implicit/default selection (full User + tags: Tag[] shape); a single-execution / no-LATERAL assertion; depth-2 nesting (invitedUsers → tags); edges (user with no tags → tags: []; a tag shared by multiple users).

Why

This is the first of the three relation-shaped M:N consumers (read / filter / write) over slice 0s shared through primitive. The correlated-only approach matches the post-TML-2729 read path (no LATERAL to reintroduce).

Scope / notes

Read only — filter (TML-2786) and write (TML-2787) are later slices. The fixture is one-directional (User.tags; reverse Tag.users deferred — adding it trips a latent create-overload type fragility in unrelated mutation-defaults tests; see the projects unattended-decisions log). Fixture re-emit used a tsx bypass because the CLI contract emit fails on a sandbox config-load env issue — CI fixtures:check is the real golden-stability gate; please confirm its green (or re-run the canonical emit). Broad integration runs show pre-existing PGlite/WASM JIT flakiness; the M:N tests pass on targeted runs.

Refs: TML-2785.

…ndard (TML-2785)

Read slice (correlated include through junction). Bakes the operator
integration-test standard (whole-row asserts, explicit select, +implicit
nested-M:N case) into the project cross-cutting requirements and slice 1.
3 dispatches: fixture M:N / read code / integration tests.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…re-emit contract

Adds a pure-junction UserTag model (userId/tagId, composite PK) and a
User.tags manyToMany relation to the sql-orm-client integration fixture.
Re-emits contract.json + contract.d.ts for both the integration test copy
and the package-local copy (with pgvector refs stripped).

The Tag model stays single-sided (no Tag.users reverse) to avoid a
MutationCreateInputWithRelations overload collision in the existing
collection-mutation-defaults tests.

Emitted via tsx script (c12 config loader unavailable in this sandbox);
verified additivity of the diff and round-trip via SqlContractSerializer.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Add many-to-many include support via a single correlated subquery that
joins the child table to the junction on junction.childColumns = child.targetColumns
and correlates to the parent via WHERE junction.parentColumns = parent.parentLocalColumns.
Composite keys AND across all column pairs. No LATERAL joins.

- IncludeExpr gains `through?: IncludeThroughDescriptor` carrying junction table
  name, parentColumns, childColumns, targetColumns, and parentLocalColumns.
- `resolveIncludeRelation` in collection-contract.ts surfaces `through` from the
  contract relation when present, resolving field names to column names for the
  parent local columns.
- `Collection.include()` propagates `through` into IncludeExpr via spread.
- `buildManyToManyJunctionArtifacts` in query-plan-select.ts builds the JOIN ON
  expression (BinaryExpr or AndExpr over child column pairs) and the correlated
  WHERE (BinaryExpr or AndExpr over parent column pairs), producing a non-lateral
  inner JoinAst to the junction table.
- `buildIncludeChildRowsSelect` detects `include.through` and uses the M:N
  artifacts instead of the FK equality WHERE; `buildDistinctNonLeafChildRowsSelect`
  receives and applies the same junction joins.
- `dispatchWithIncludes` in collection-dispatch.ts forces all `through.parentLocalColumns`
  (not just `localColumn`) into the parent SELECT augmentation for composite M:N keys.
- `buildManyToManyContract` test helper and M:N unit tests covering single-column
  and composite-key junction shapes, plus a FK path non-regression test.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…ManyJunctionArtifacts; add M:N+distinct+non-leaf test

F1: the two bare `as AnyExpression` casts in `buildManyToManyJunctionArtifacts` are replaced with
`castAs<AnyExpression>(…!)` — BinaryExpr is a union member of AnyExpression, so the assertion is
type-checked; the non-null assertion is safe because the branch is only taken when `length === 1`.
Adds the `castAs` import from `@prisma-next/utils/casts`. `lint:casts` delta: -1.

F2: new unit test `attaches junction join to baseInner in M:N + distinct + nested non-leaf path`
in the `M:N include correlated subquery` describe block. Constructs a contract with
parents→children (M:N via parent_child junction) + children→grandchildren (FK), sets
`distinct: ['name']` and a nested grandchild include on the M:N IncludeExpr, calls
`compileSelectWithIncludes`, and asserts:
  - junction join (`INNER JOIN parent_child`, `lateral: false`) attaches to `baseInner`
    (the innermost scalar SELECT inside the ROW_NUMBER wrap), not to the dedup wrapper or
    outer distinct SELECT
  - correlated WHERE (`parent_child.parent_id = parents.id`) is present at `baseInner`
  - no junction join leaks to `innerSelect` or the outer `childRows`

All 493 tests pass; typecheck clean.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Adds `mn-include.test.ts` covering the end-to-end M:N include path for
`User.tags` via the `user_tags` junction, using the PGlite harness.

Tests satisfy the operator integration-test standard:
- Whole-row toEqual assertions on every test
- Explicit .select() on 6/7 tests
- One implicit/default-selection test (full User + tags: Tag[] shape)
- Single-execution assertion + no LATERAL in emitted SQL
- Depth-2: M:N tags nested under invitedUsers (1:N self-relation)
- Sibling depth-2: include("tags") alongside include("posts") in one execution
- Edge: user with no tags returns tags: []
- Edge: tag shared by multiple users resolves independently for each

Also extends `setupTestSchema` and adds `seedTags`/`seedUserTags` helpers
to `runtime-helpers.ts` to support the junction table in integration tests.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
… (TML-2785)

Orchestrator artifacts for the read slice (fixture / read-path / integration
dispatches; read-path took 3 rounds incl. a truncation recovery). Review log
under reviews/ is gitignored.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
@tensordreams tensordreams requested a review from a team as a code owner June 1, 2026 19:16
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: d5bd9775-b3ce-4f39-8195-7513157b560d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2785-slice-1-correlated-read

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

size-limit report 📦

Path Size
postgres / no-emit 136.07 KB (+0.21% 🔺)
postgres / emit 125.74 KB (+0.23% 🔺)
mongo / no-emit 75.69 KB (0%)
mongo / emit 70.68 KB (0%)

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 1, 2026

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@679

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@679

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@679

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@679

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@679

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@679

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@679

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@679

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@679

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@679

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@679

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@679

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@679

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@679

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@679

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@679

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@679

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@679

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@679

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@679

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@679

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@679

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@679

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@679

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@679

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@679

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@679

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@679

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@679

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@679

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@679

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@679

prisma-next

npm i https://pkg.pr.new/prisma-next@679

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@679

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@679

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@679

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@679

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@679

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@679

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@679

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@679

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@679

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@679

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@679

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@679

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@679

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@679

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@679

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@679

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@679

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@679

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@679

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@679

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@679

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@679

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@679

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@679

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@679

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@679

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@679

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@679

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@679

commit: 4ee6af6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant